#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

uint8_t buf[0x10000];

uint8_t checksum(uint8_t *data, size_t n)
{
	uint8_t ret = 0;
	size_t i;
	for (i=0; i<n; i++)
		ret -= data[i];
	return ret;
}

void convert(FILE *in, FILE *out, int rowsize, int big_endian)
{
	assert(!(rowsize&3) && rowsize <= 16);
	uint8_t outbuf[21];
	size_t remaining = fread(buf, 1, sizeof buf, in);
	size_t addr = 0;
	size_t i;
	while (remaining >= rowsize) {
		/* data byte count */
		outbuf[0] = rowsize;
		size_t row = addr / rowsize;
		/* hibyte of row */
		outbuf[1] = row >> 8;
		/* lobyte of row */
		outbuf[2] = row;
		/* record type: DATA */
		outbuf[3] = 0;
		/* data */
		if (big_endian) {
			for (i=0; i<rowsize; i+=4) {
				outbuf[4+i+0] = buf[addr+i+3];
				outbuf[4+i+1] = buf[addr+i+2];
				outbuf[4+i+2] = buf[addr+i+1];
				outbuf[4+i+3] = buf[addr+i+0];
			}
		} else {
			for (i=0; i<rowsize; i++)
				outbuf[4+i] = buf[addr+i];
		}
		/* checksum */
		size_t n = 4+rowsize;
		outbuf[n] = checksum(outbuf, n);
		/* write outbuf to file */
		fputc(':', out);
		for (i=0; i<=n; i++)
			fprintf(out, "%02x", outbuf[i]);
		fputc('\n', out);

		addr += rowsize;
		remaining -= rowsize;
	}
	if (remaining) {
		/* data byte count */
		outbuf[0] = remaining;
		size_t row = addr / rowsize;
		/* hibyte of row */
		outbuf[1] = row >> 8;
		/* lobyte of row */
		outbuf[2] = row;
		/* record type: DATA */
		outbuf[3] = 0;
		/* data */
		if (big_endian) {
			assert(!(remaining&3));
			for (i=0; i<remaining; i+=4) {
				outbuf[4+i+0] = buf[addr+i+3];
				outbuf[4+i+1] = buf[addr+i+2];
				outbuf[4+i+2] = buf[addr+i+1];
				outbuf[4+i+3] = buf[addr+i+0];
			}
		} else {
			for (size_t i=0; i<remaining; i++)
				outbuf[4+i] = buf[addr+i];
		}
		/* checksum */
		size_t n = 4+remaining;
		outbuf[n] = checksum(outbuf, n);
		/* write outbuf to file */
		n = n+1;
		fputc(':', out);
		for (i=0; i<n; i++)
			fprintf(out, "%02x", outbuf[i]);
		fputc('\n', out);
	}
	/* EOF record */
	fprintf(out, ":00000001ff\n");
}

void usage(void)
{
	fprintf(stderr, "usage: bintomcs infile outfile\n");
	exit(2);
}

int main(int argc, char **argv)
{
	if (argc != 3)
		usage();
	/* input file, binary */
	FILE *fp = fopen(argv[1], "rb");
	if (!fp) {
		perror(argv[1]);
		return 1;
	}
	/* output file, Intel HEX */
	FILE *fp2 = fopen(argv[2], "w");
	if (!fp2) {
		perror(argv[2]);
		return 1;
	}

	/* ensure file size < 64K */
	fseek(fp, 0, SEEK_END);
	if (ftell(fp) >= 65536L) {
		fprintf(stderr, "file too large\n");
		return 1;
	}

	fseek(fp, 0, SEEK_SET);
	convert(fp, fp2, 4, 1);
	return 0;
}
